#include "function-model.h"
#include "economy.h"
#include "hashthing.h"

void InitFunctionModel() {
  static int inited = 0; if (inited++) return;  
  InitProblem();
  InitRestaurant(); }

void FunctionModel::AddBase(FunctionModel *base) {
  revision_ = base->revision_;
  CHECK(problem_ == base->problem_);
  base_ = base;
  table_->AddBase(base_->table_); }

void FunctionModel::InitNewModel() {
  return;
  /*
  for (int64 i=0; i<problem_->NumFunctions(); i++) {
    if ((i % 1000) == 0)
      cout << "::InitNewModel " << i << endl;
    Thing function = problem_->NthFunction(i);
    int64 num_args = problem_->NumArgs(function, ALL_ARGUMENTS);
    for (int64 j=0; j<num_args; j++) {
      Thing arg = problem_->NthArg(function, j, ALL_ARGUMENTS);
      Thing implicit_result = problem_->ImplicitResult(function, arg);
      AddFunctionDefinition(function, implicit_result, 1);
    }
    AddFunction(function, num_args);
  }
  */
}

void FunctionModel::AddFunction(Thing function, int64 delta) {
  TableSet(table_, FUNCTIONS.ID()).Update(function, delta); }

// special: don't add args for problem calls
void FunctionModel::AddFunctionArgs(Thing function, Thing args, int64 delta) {
  Thing args_type = problem_->ArgsType(function, args);
  if (args_type != NIL) return;
  int64 key = Thing::MakeArrayID2(FUNCTION_ARGS, function);
  TableSet(table_, key).Update(args, delta); }

void FunctionModel::AddFunctionDefinition
(Thing function, Thing definition, int64 delta) {
  TableSet definitions(table_, Thing::MakeArrayID2(DEFINITIONS, function));
  definitions.Update(definition, delta);
  int64 definition_diff = 
    ExistsDiff(definitions.old_count_, definitions.new_count_);
  if (definition_diff) gdl_.Add(definition, definition_diff);
  int64 restaurant_dl_diff =
    ImmutableRestaurantUpdate(definitions.old_count_, 
			      definitions.new_count_,
			      definitions.old_weight_,
			      definitions.old_size_,
			      *G_THETAS);
  UpdateDL(restaurant_dl_diff);
}

// Dependencies is a hash, the values are the calls that
// this call depends on. So it has to be inserted as a dependent of theirs
void FunctionModel::AddDependencies(Thing call, Thing dependencies, int64 delta) { 
  for (HashIterator i(dependencies); !i.AtEnd(); ++i) {
    Thing dependency = i.GetValue();
    TableSet dependents
      (table_, Thing::MakeArrayID2(DEPENDENTS, dependency));
    dependents.Update(call, delta);
    UpdateMissing(dependency);
  }
}

Thing FunctionModel::ImplicitResult(Thing fname, Thing args) {
  if (! (args.IsArray() && (args.ArraySize() == 1))) return NULL;
  return problem_->ImplicitResult(fname, args.Ref(0));
}

int64 FunctionModel::GetDL() { 
  return TableIntegerGet(table_, MODELDL.ID()); }

void FunctionModel::UpdateDL(int64 delta) {
  TableIntegerAdd(table_, MODELDL.ID(), delta); }

int64 FunctionModel::Utility() {
  TableSet missing_calls(table_, MISSING_CALLS.ID());  
  TableSet early_calls(table_, EARLY_CALLS.ID());  
  if (missing_calls.size() + early_calls.size() > 0) 
    return -1000000000000000000ll 
      - missing_calls.size() - early_calls.size();
  return -GetDL(); }

Thing FunctionModel::GetXTime(Thing function, Thing args)  {
  return ThingPtrToThing
    (table_->Lookup(Thing::MakeArrayID3(XTIME, function, args))); }

Thing FunctionModel::CallGetXTime(Thing call) {
  Thing xtime = GetXTime(call.Ref(0), call.Ref(1));
  if (xtime.Is() && (xtime.Ref(0).Ref(0).ID() == call.ID())) return xtime;
  return NULL;
}

Thing FunctionModel::CallGetTime(Thing call) {
  Thing xtime = CallGetXTime(call);
  if (xtime.Is()) return xtime.Ref(1);
  return NULL;
}

// xtime has already been verified.
// clobbers any conflicting execution
void FunctionModel::AddXTime(Thing xtime, ThreadAllocator *a) {
  Thing execution = xtime.Ref(0);
  Thing time = xtime.Ref(1);
  Thing call = execution.Ref(0);
  Thing definition = execution.Ref(1);
  Thing dependencies = execution.Ref(2);
  Thing fname = call.Ref(0);
  Thing args = call.Ref(1);
  Thing result = call.Ref(2);

  Thing old_xtime = GetXTime(fname, args);
  if (old_xtime.Is()) {
    // There exists a conflicting execution

    if (old_xtime == xtime) {
      // Same result, definition and time ...
      return;
    }

    Thing old_execution = old_xtime.Ref(0);
    if (execution == old_execution) {
      // Same result, same definition, different time
      (*table_)[Thing::MakeArrayID3(XTIME, fname, args)] = xtime;
      UpdateErrorsTimeChanged(call);
      return;
    }

    Thing old_call = old_execution.Ref(0);
    if (old_call == call) { 
      // Same result, different definition
      ReplaceXTimeSameResult(old_xtime, xtime);
      return; 
    }
    
    // different result
    SimpleAddXTime(old_xtime, -1);
    UpdateErrorsExistenceChanged(old_call);
    SimpleAddXTime(xtime, 1);
    UpdateErrorsExistenceChanged(call);
    return;
  }
  
  Thing implicit_result = ImplicitResult(fname, args);
  SimpleAddXTime(xtime, 1);
  if (implicit_result.Is()) {
    AddFunction(fname, -1);
    AddFunctionDefinition(fname, implicit_result, -1);
    
    if (implicit_result == result) {    
      // implicit result matches result
      UpdateErrorsTimeChanged(call);
      return;
    }

    if (implicit_result.Is()) {
      // implicit result exists.
      Thing implicit_call = a->MakeImmutableArray3(LOCAL_ALLOCATION, fname, args, implicit_result);
      UpdateErrorsExistenceChanged(implicit_call);
      UpdateErrorsExistenceChanged(call);
      return;
    }
  }

  // no implicit result
  UpdateErrorsExistenceChanged(call);
  return;
}

// Same result, different definition and possibly different time
void FunctionModel::ReplaceXTimeSameResult(Thing old_xtime, Thing xtime) {
  Thing execution = xtime.Ref(0);
  Thing old_execution = old_xtime.Ref(0);
  Thing definition = execution.Ref(1);
  Thing old_definition = old_execution.Ref(1);
  Thing dependencies = execution.Ref(2);
  Thing old_dependencies = old_execution.Ref(2);
  Thing call = execution.Ref(0);
  Thing function = call.Ref(0);
  Thing args = call.Ref(1);

  AddFunctionDefinition(function, old_definition, -1);
  AddFunctionDefinition(function, definition, 1);
  (*table_)[Thing::MakeArrayID3(XTIME, function, args)] = xtime;
  AddDependencies(call, old_dependencies, -1);
  AddDependencies(call, dependencies, 1);
  // TODO: if the time didn't change, we only have to call UpdateEarly 
  // on this call, instead of calling UpdateErrorsTimeChanged.
  UpdateErrorsTimeChanged(call); 
}

void FunctionModel::SimpleAddXTime(Thing xtime, int64 delta) {
  // cout << "::SimpleAddXTime " << delta << endl;
  // cout << xtime << endl;

  Thing execution = xtime.Ref(0);
  Thing time = xtime.Ref(1);
  Thing call = execution.Ref(0);
  Thing definition = execution.Ref(1);
  Thing dependencies = execution.Ref(2);
  Thing function = call.Ref(0);
  Thing args = call.Ref(1);
  Thing result = call.Ref(2);

  AddFunction(function, delta);
  AddFunctionArgs(function, args, delta);
  
  AddFunctionDefinition(function, definition, delta);
  if (delta == 1) {
    (*table_)[Thing::MakeArrayID3(XTIME, function, args)] = xtime;
  } else {
    table_->Delete(Thing::MakeArrayID3(XTIME, function, args));
  }
  AddDependencies(call, dependencies, delta);
}

// Verify the xtime exists and call SimpleAddXTime
bool FunctionModel::RemoveXTime(Thing xtime, ThreadAllocator *a) {
  Thing execution = xtime.Ref(0);
  Thing call = execution.Ref(0);
  Thing fname = call.Ref(0);
  Thing args = call.Ref(1);
  Thing result = call.Ref(2);
  Thing xtime_verify = GetXTime(fname, args);
  if (xtime_verify.IsNot() || (xtime_verify != xtime)) return false;
  
  Thing implicit_result = ImplicitResult(fname, args);
  SimpleAddXTime(xtime, -1);
  if (implicit_result.Is()) {
    AddFunction(fname, 1);
    AddFunctionDefinition(fname, implicit_result, 1);
    
    if (implicit_result == result) {
      // implicit result matches result
      UpdateErrorsTimeChanged(call);
      return true;
    }

    if (implicit_result.Is()) {
      // implicit result exists.
      Thing implicit_call = a->MakeImmutableArray3(LOCAL_ALLOCATION, fname, args, implicit_result);
      UpdateErrorsExistenceChanged(implicit_call);
      UpdateErrorsExistenceChanged(call);
      return true;
    }
  }

  // no implicit result
  UpdateErrorsExistenceChanged(call);
  return true;
}


// Another optimization is to pass the direction of time change
void FunctionModel::UpdateErrorsTimeChanged(Thing call) {
  // cout << "::UpdateErrorsTimeChanged" << endl;
  // cout << call << endl;
  UpdateEarly(call);
  TableSet dependents(table_, Thing::MakeArrayID2(DEPENDENTS, call));
  for (uint64 i=0; i<dependents.size(); i++)
    UpdateEarly(dependents.nth(i)); }

void FunctionModel::UpdateErrorsExistenceChanged(Thing call) {
  // cout << "::UpdateErrorsExistenceChanged " << endl;
  // cout << call << endl;
  UpdateErrorsTimeChanged(call);
  UpdateMissing(call);
}

void FunctionModel::UpdateMissing(Thing call) {
  TableSet(table_, MISSING_CALLS.ID())
    .SetCount(call, IsMissing(call)); }

void FunctionModel::UpdateEarly(Thing call) {
  TableSet(table_, EARLY_CALLS.ID())
    .SetCount(call, IsEarly(call)); }

int64 FunctionModel::IsMissing(Thing call) {
  if (CallExists(call)) return 0;
  if (TableSet(table_, 
	       Thing::MakeArrayID2(DEPENDENTS, call)).size()) return 1;
  return 0; 
}

// Else, if any existing dependencies are not earlier than the call, it is early. 
int64 FunctionModel::IsEarly(Thing call) {
  Thing xtime = CallGetXTime(call);
  // This deals with implicitly represented problem calls
  if (xtime.IsNot()) return 0;
  Thing call_time = xtime.Ref(1);
  Thing execution = xtime.Ref(0);
  Thing dependencies = execution.Ref(2);
  for (HashIterator i(dependencies); !i.AtEnd(); ++i) {
    Thing dep = i.GetValue();
    Thing dep_time = CallGetTime(dep);
    if (dep_time.Is() && 
	(dep_time.GetInteger() >= call_time.GetInteger())) return 1;
  }
  return 0;
}

// returns NULL if there is no current result. 
Thing FunctionModel::Result(Thing function, Thing args) {
  Thing xtime = GetXTime(function, args);
  if (xtime.Is()) {
    Thing x = xtime.Ref(0);
    Thing call = x.Ref(0);
    Thing result = call.Ref(2);
    return result;
  }
  if (args.IsArray() && args.ArraySize() == 1) {
    return problem_->ImplicitResult(function, args.Ref(0));
  }
  return NULL;
}

// Assumes call is an array of size 3 whose arg is an array
bool FunctionModel::CallExists(Thing call) {
  return (Result(call.Ref(0), call.Ref(1)) == call.Ref(2)); }

bool FunctionModel::ConflictsWithTrainingData(Thing call) {
  Thing type = problem_->ArgsType(call.Ref(0), call.Ref(1));
  if (type != TRAINING_ARGUMENT) return false;
  Thing problem_result = problem_->Result(call.Ref(0), call.Ref(1).Ref(0));
  if (problem_result.IsNot()) {
    cout << call << endl;
    CHECK(false);
  }
  return (problem_result != call.Ref(2));
}

Solution::Solution(Problem * problem) :
  problem_(problem),
  table_(1 << 20, false, NULL),
  model_(problem, &table_) {
  model_.InitNewModel();
  G_ALLOC->AddDependant(this); }

Solution::Solution(Problem * problem, string file_prefix) :
  problem_(problem),
  table_(file_prefix + ".table"),
  model_(problem, &table_) {
  G_ALLOC->AddDependant(this); }

void Solution::Store(string prefix) {
  table_.Store(prefix + ".table"); }

void HashTableFlagReferencesH
(HashTable* h,
 int32 shard, int32 num_shards, Frontier * frontier) {
  pair<uint64, uint64> se = Shard(h->size_, num_shards, shard);
  for (uint64 i=se.first; i<se.second; i++) {
    HashTable::Cell c = h->data_[i];
    if (!c.IsValid()) continue;
    Thing t = (Thing)c.value_;
    if (t.IsUnboxed()) continue;
    Box * b = t.GetBox();
    if (!b) continue;
    frontier->Add(t);
  }
}

void Solution::FlagReferences(int32 shard, Frontier * frontier) {
  HashTableFlagReferencesH
    (table_.GetHashTable(), shard, NumShards(), frontier); }

ThreadAllocator * SolutionThread::A() { return et_->a_; }

void SolutionThread::Commit() {
  table_.Commit();
  solution_->model_.revision_++; }

void SolutionThread::Globalize() {
  HashTable * h = &(table_.h_);
  for (uint64 i=0; i<h->size_; i++) {
    HashTable::Cell & c_ref = h->data_[i];
    if (c_ref.IsValid() && (c_ref.value_ != kOverlayDeletedValue)) {
      Thing debug_thing = Thing(c_ref.value_);
      CHECK(c_ref.value_);
      c_ref.value_ = et_->a_->Globalize(Thing(c_ref.value_)).ID();
    }
  }
}

bool SolutionThread::BadOverlay() {
  if (overlay_.revision_ == overlay_.base_->revision_) return false;
  if ((!overlay_.locked_) && overlay_.table_->OverlayIsEmpty()) {
    overlay_.revision_ = overlay_.base_->revision_;
    return false;
  }
  return true; }

Thing SolutionThread::BadOverlayCall() { return BadOverlay() ? T : NIL; }

ExecutionState::ExecutionState(SolutionThread *st) : st_(st) {}

ThreadAllocator * ExecutionState::A() { return st_->A(); }

EconomyThread * ExecutionState::ET() { return st_->et_;}

// returns true if the execution is valid. 
bool ExecutionState::Verify(Thing x) { 
 // C++ Verification setup
  if (!IsArrayOfSize(x, 3) || x.IsMutable()) return false;
  dependencies_ = x.Ref(2);
  if (!IsHash(dependencies_)) return false;
  Thing call = x.Ref(0);
  if (!IsArrayOfSize(call, 3)) return false;
  Thing args = call.Ref(1);
  if (!args.IsArray()) return false;
  if (st_->overlay_.ConflictsWithTrainingData(call)) return false;
  
  Thing code = 
    A()->MakeImmutableArray2
    (LOCAL_ALLOCATION, 
     KERNEL_VERIFY,
     A()->MakeImmutableArray2(LOCAL_ALLOCATION, QUOTE, x));
  
  Thing verified = 
    st_->et_->vi_->SafeExecute(code, 1000000000000ll, kGenerativeMode, ALL);

  return (verified == T);
}

Thing ExecutionState::ModelCall(Thing function, Thing args) {
#define MODELCALL_FAIL { cout << "modelcall fail function: " << function \
  << " args: " << args << " dependencies: " << dependencies_ << endl; \
  throw VERIFY_EXECUTION_EXCEPTION; }
  // cout << "::XS::ModelCall" << endl;
  Thing result 
    = HashLookDefault
    (dependencies_, Thing(Thing::MakeArrayID2(function, args)), 
     NULL, NULL);
  // cout << "XS::ModelCall result:" << result << endl;
  if (result.IsNot()) MODELCALL_FAIL;  
  return result.Ref(2);
}

BIND_HASH(kVerify);

bool SolutionThread::CacheVerifyExecution(Thing x) {
  // Don't verify executions that conflict with the model
  uint64 key = Fingerprint(x.ID(), kVerify);
  uint64 result_i;
  if (A()->cache_.Lookup(key, &result_i)) 
    return true;
  bool result = xs_.Verify(x);
  if (result) A()->cache_.Store(key, 1);
  return result; }

Thing SolutionThread::SystemCall(Syscall which, int num_parameters) {
  // try asking the problem
  Thing problem_result = 
    solution_->problem_->SystemCall(et_, which, num_parameters);
  if (problem_result != NOTIMPLEMENTED) return problem_result;

  Thing param0 = NIL, param1 = NIL, param2 = NIL, param3 = NIL;
  if (num_parameters > 0) param0 = et_->vi_->GetParameter(0, num_parameters);
  if (num_parameters > 1) param1 = et_->vi_->GetParameter(1, num_parameters);
  if (num_parameters > 2) param2 = et_->vi_->GetParameter(2, num_parameters);
  if (num_parameters > 3) param3 = et_->vi_->GetParameter(3, num_parameters);

  // DEBUG(which) DEBUG(param);
  Syscall call = (Syscall)which;
  et_->vi_->NoFunctionCache();
  try {
    switch (call) {
      // generative mode
    case SYSCALL_MODELCALL: return xs_.ModelCall(param0, param1);
      // overlays
    case SYSCALL_NEWOVERLAY: return NewOverlayCall(param0);
    case SYSCALL_BADOVERLAY: return BadOverlayCall();
      // mutate model
    case SYSCALL_ADDXTIME: return AddXTimeCall(param0); 
    case SYSCALL_REMOVEXTIME: return RemoveXTimeCall(param0);
      
      // explore model
    case SYSCALL_TABLESET: return TableSetCall(param0, param1, param2);
    case SYSCALL_TABLELOOK: return TableLookCall(param0, param1);
    default: return NOTIMPLEMENTED;
    }
  } catch (HashTable::FullException e) {
    overlay_.revision_ = overlay_.base_->revision_ - 1;
    throw BAD_OVERLAY_EXCEPTION;
  }
}

Thing SolutionThread::TableLookCall(Thing key, Thing default_result) {
  // cout << "::TableLookCall\n";
  RWLock::ReadLocker rl(&reader_); 
  if (BadOverlay()) throw BAD_OVERLAY_EXCEPTION;
  const Thing * result = table_.Lookup(key.ID());
  if (result) return *result;
  return default_result;
}

Thing SolutionThread::TableSetCall(Thing action, 
				   Thing set_description, 
				   Thing param) {
#define TABLESETRETURN(val) { return val; }
  RWLock::ReadLocker rl(&reader_); 
  if (BadOverlay()) throw BAD_OVERLAY_EXCEPTION;
  TableSet ts(&table_, set_description.ID());
  if (action == NTH) {
    if (!param.IsInteger()) throw TYPE_EXCEPTION;
    Thing ret = ts.fallable_nth(param.GetInteger());
    if (ret.IsNot()) throw TABLESET_EXCEPTION;
    TABLESETRETURN(ts.fallable_nth(param.GetInteger()));
  }
  if (action == COUNT) 
    TABLESETRETURN(Thing::MakeInteger(ts.Count(param)));
  if (action == CONTAINS) 
    TABLESETRETURN((ts.Count(param) ? T : NIL));
  if (action == SIZE)
    TABLESETRETURN(Thing::MakeInteger(ts.size()));
  if (action == WEIGHT)
    TABLESETRETURN(Thing::MakeInteger(ts.weight()));
  throw UNKNOWN_SYSCALL_EXCEPTION;
}

Thing SolutionThread::NewOverlayCall(Thing locked) {
  RWLock::ReadLocker rl(&reader_);
  overlay_.AddBase(&(solution_->model_));
  overlay_.locked_ = (locked != NIL);
  return NIL; }

Thing SolutionThread::AddXTimeCall(Thing xtime) {
  if (!(xtime.IsArray())) throw TYPE_EXCEPTION;
  if (xtime.ArraySize() != 2) throw TYPE_EXCEPTION;
  Thing execution = xtime.Ref(0);
  Thing time = xtime.Ref(1);
  if (!time.IsInteger()) throw TYPE_EXCEPTION;
  if (!CacheVerifyExecution(execution)) throw VERIFY_EXECUTION_EXCEPTION;
  RWLock::ReadLocker rl(&reader_); 
  if (BadOverlay()) throw BAD_OVERLAY_EXCEPTION;
  overlay_.AddXTime(xtime, A());
  return T; }

Thing SolutionThread::RemoveXTimeCall(Thing xtime) {
  RWLock::ReadLocker rl(&reader_); 
  if (BadOverlay()) throw BAD_OVERLAY_EXCEPTION;
  if (!overlay_.RemoveXTime(xtime, A())) throw MODEL_EXCEPTION;
  return T; }

FunctionModel * SolutionThread::BaseModel() {
  return overlay_.base_; }

Thing SolutionThread::Test(Thing maybe_force) {

  return Thing::MakeInteger(1000);

  /*
  int64 old_utility, new_utility, overlay_diff;
  {
    RWLock::ReadLocker rl(&reader_);
    if (BadOverlay()) { throw BAD_OVERLAY_EXCEPTION; }
    old_utility = BaseModel()->Utility();
    new_utility = overlay_.Utility();
    overlay_diff = new_utility - old_utility;
    if ((maybe_force != FORCE) &&
	(overlay_diff <= (int64) G_EC->accept_threshold_)) {
      // cout << "overlay_diff = " << overlay_diff << endl;
      // cout << "accept_threshold_ = " << G_EC->accept_threshold_ << endl;
      return Thing::MakeInteger(0);
    }
  }

  int64 num_improvements;
  {
    RWLock::WriteLocker wl(&reader_);
    if (BadOverlay()) {
      throw BAD_OVERLAY_EXCEPTION;
    }
    Globalize();
    Commit();
    overlay_.AddBase(&(solution_->model_));
    num_improvements = 
      TableIntegerAdd(BaseModel()->table_, NUMIMPROVEMENTS.ID(), 1);
  }

  cout << "#improv: " << num_improvements
       << " dl: " << Delimit(-new_utility)
       << " diff: " << Delimit(overlay_diff) << endl;
  cout << "G_ALLOC->...MemoryUsed(): " 
       << Delimit(G_ALLOC->AccurateRunningMemoryUsed()) << endl;
  cout << "#ad tuples: "
       << Delimit(G_EC->ad_store_.used_tuples_) << endl;

  // don't return negative number if you want the change to stick
  // so need to do this for FORCE calls
  return Thing::MakeInteger(max(overlay_diff, 1ll));
  */
}
